'''
Created on Aug 27, 2010

@author: riche
'''
import unittest
from ft.framework import abstraction, component

class TestComp1(component.Implementation):
    
    def __init__(self, name):
        component.Implementation.__init__(self, name)
        self.add_port(component.Port('in', 'handler'))
        self.add_port(component.Port('out', '__OUT__'))
        
    def handler(self, msg):
        self.send_out('out', msg)

class TestAbs1(abstraction.Abstraction):
    
    def __init__(self, name):
        abstraction.Abstraction.__init__(self, name)
        self.add_port('in', None)
        
class TestAbs2(abstraction.Abstraction):
    
    def __init__(self, name):
        abstraction.Abstraction.__init__(self, name)
        comp = TestComp1('test_comp')
        self.add_component(comp)
        self.add_port('in', (comp, 'in'))
        self.add_port('bad_in', None)
        
class TestAbs2a(abstraction.Abstraction):
    
    def __init__(self, name):
        abstraction.Abstraction.__init__(self, name)
        comp = TestComp1('test_comp')
        self.add_component(comp)
        self.add_port('in', (comp, 'in'))
        self.add_port('out', (comp, 'out'), True)
        
class TestAbs3(abstraction.Abstraction):
    
    def __init__(self, name):
        abstraction.Abstraction.__init__(self, name)
        comp = TestAbs2('test_inner_abs')
        self.add_component(comp)
        self.add_port('in', (comp, 'in'))
        self.add_port('out', (comp, 'out'), True)

class Test(unittest.TestCase):
    
    def test_add_port_unimplemented(self):
        '''The Port name should be in the port list after calling add_port'''
        abs = abstraction.Abstraction('test_abs')
        abs.add_port('in', None)
        self.assertIn('in', abs._ports.keys())
        
    def test_add_port_implemented(self):
        '''The port name and imp (comp, port pair) should be in port list and there should be a connection'''
        abs = abstraction.Abstraction('test_abs')
        comp = component.Implementation('test_comp')
        comp.add_port(component.Port('in', 'handler'))
        abs.add_component(comp)
        abs.add_port('in', (comp, 'in'))
        self.assertIn('in', abs._ports.keys())
        self.assertNotEqual(abs._ports['in'], None)
        self.assertNotEqual(abs._connections['test_abs:in'], None)
        self.assertEqual(abs._connections['test_abs:in'].get_name(), 'test_abs:in->test_comp:in')
        
    def test_add_output_port(self):
        '''If a port is an output port, it must be labeled as such.'''
        abs = abstraction.Abstraction('test_abs')
        comp = TestComp1('test_comp')
        abs.add_component(comp)
        abs.add_port('out', (comp, 'out'), output=True)
        self.assertIn('out', abs._ports.keys())
        self.assertEqual(abs._ports['out'].get_handler(), '__OUT__', )
        self.assertNotEqual(abs._ports['out'], None)
        self.assertNotEqual(abs._connections['test_comp:out'], None)
        self.assertEqual(abs._connections['test_comp:out'].get_name(), 'test_comp:out->test_abs:out')
        
    def test_resolve_endpoint(self):
        '''Should return the correct Connector object'''
        abs = abstraction.Abstraction('test_abs')
        comp = TestComp1('test_comp')
        abs.add_component(comp)
        abs.add_port('in', (comp, 'in'))
        conn = abs.resolve_endpoint('in')
        self.assertNotEqual(conn, None)
        self.assertEqual(conn.get_name(), 'test_abs:in->test_comp:in')
        
    def test_resolve_nested_abs_endp(self):
        '''Should recurse through nested abstractions until it hits an implementation.'''
        out_abs = abstraction.Abstraction('out_abs')
        in_abs = abstraction.Abstraction('in_abs')
        comp = TestComp1('test_comp')
        out_abs.add_component(in_abs)
        in_abs.add_component(comp)
        in_abs.add_port('in', (comp, 'in'))
        out_abs.add_port('in', (in_abs, 'in'))
        conn = out_abs.resolve_endpoint('in')
        self.assertNotEqual(conn, None)
        self.assertEqual(conn.get_dest_name(), 'test_comp:in')

        
    def test_resolve_bad_endpoint(self):
        '''An error should be raised if no endpoint exists.'''
        abs = abstraction.Abstraction('test_abs')
        comp = TestComp1('test_comp')
        abs.add_component(comp)
        abs.add_port('in', (comp, 'in'))
        self.assertRaises(component.UnknownPortError, abs.resolve_endpoint, 'fail_port')
        
    def test_add_component(self):
        '''The component should be in the abstraction's comp list after calling add_component.'''
        abs = abstraction.Abstraction('test_abs')
        comp = component.Implementation('test_comp')
        comp.add_port(component.Port('in', 'handler'))
        abs.add_component(comp)
        self.assertIn(comp.get_name(), abs._components.keys())
        self.assertEqual(comp, abs._components[comp.get_name()])    
        
    def test_get_endpoint(self):
        '''Test the get endpoing method returns the correct endpoint'''
        abs = abstraction.Abstraction('test_abs')
        comp = TestComp1('test_comp')
        abs.add_component(comp)
        abs.add_port('in', (comp, 'in'))
        endp = abs.get_endpoint('in')
        self.assertNotEqual(endp, None)
        self.assertEqual(endp.get_name(), 'test_abs:in')
        
    def test_get_bad_endpoint(self):
        '''Should throw an error if named port doesn't exist.'''
        abs = abstraction.Abstraction('test_abs')
        comp = TestComp1('test_comp')
        abs.add_component(comp)
        abs.add_port('in', (comp, 'in'))
        self.assertRaises(component.UnknownPortError, abs.get_endpoint, 'fail_port')

    def test_unimplemented(self):
        '''An abstraction should throw an exception on run.
        
           Abstractions are purely design containers. During startup, all abstraction 
           boundaries will be eliminated. No Abstraction instance should ever receive
           a message for execution.    
        '''
        abs = TestAbs1('test_abs')
        self.assertRaises(component.UnimplementedAbstractionError, abs.run_component, 'in', 'MSG_STRING')
    
    def test_mp_uimplemented(self):
        '''Regardless of the number of ports, an Abstraction should fail on execution. 
        
           See note for test_unimplemented.
        '''
        abs = TestAbs2('test_abs')
        self.assertRaises(component.UnimplementedAbstractionError, abs.run_component, 'bad_in', 'MSG_STRING')
        
    def test_add_noncomponent(self):
        '''If an object is added that is not a component, an exception should be thrown.'''
        fake_comp = 'test'
        abs = TestAbs1('test_abs')
        self.assertRaises(component.NotAComponentError, abs.add_component, fake_comp)
        
    def test_resolve_simple(self):
        '''Should get a list of components and "clean" connectors'''
        abs = abstraction.Abstraction('test_abs')
        comp = TestComp1('test_comp')
        abs.add_component(comp)
        abs.add_port('in', (comp, 'in'))
        comp_list, conn_list = abs.resolve()
        self.assertEqual(1, len(comp_list))
        self.assertEqual(0, len(conn_list))

if __name__ == "__main__":
    #import sys;sys.argv = ['', 'Test.testName']
    unittest.main()